*/ public function register(): array { return [ T_CLASS, T_ANON_CLASS, T_TRAIT, T_ENUM, ]; } /** * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint * @param int $classPointer */ public function process(File $phpcsFile, $classPointer): void { $this->linesCountBeforeFirstUse = SniffSettingsHelper::normalizeInteger($this->linesCountBeforeFirstUse); $this->linesCountBeforeFirstUseWhenFirstInClass = SniffSettingsHelper::normalizeNullableInteger( $this->linesCountBeforeFirstUseWhenFirstInClass, ); $this->linesCountBetweenUses = SniffSettingsHelper::normalizeInteger($this->linesCountBetweenUses); $this->linesCountAfterLastUse = SniffSettingsHelper::normalizeNullableInteger($this->linesCountAfterLastUse); $this->linesCountAfterLastUseWhenLastInClass = SniffSettingsHelper::normalizeInteger($this->linesCountAfterLastUseWhenLastInClass); $usePointers = ClassHelper::getTraitUsePointers($phpcsFile, $classPointer); if (count($usePointers) === 0) { return; } $this->checkLinesBeforeFirstUse($phpcsFile, $usePointers[0]); $this->checkLinesAfterLastUse($phpcsFile, $usePointers[count($usePointers) - 1]); $this->checkLinesBetweenUses($phpcsFile, $usePointers); } private function checkLinesBeforeFirstUse(File $phpcsFile, int $firstUsePointer): void { $tokens = $phpcsFile->getTokens(); $useStartPointer = $firstUsePointer; /** @var int $pointerBeforeFirstUse */ $pointerBeforeFirstUse = TokenHelper::findPreviousNonWhitespace($phpcsFile, $firstUsePointer - 1); if (in_array($tokens[$pointerBeforeFirstUse]['code'], Tokens::$commentTokens, true)) { $pointerBeforeFirstUse = TokenHelper::findPreviousEffective($phpcsFile, $pointerBeforeFirstUse - 1); $useStartPointer = TokenHelper::findNext($phpcsFile, Tokens::$commentTokens, $pointerBeforeFirstUse + 1); } $isAtTheStartOfClass = $tokens[$pointerBeforeFirstUse]['code'] === T_OPEN_CURLY_BRACKET; $whitespaceBeforeFirstUse = ''; if ($pointerBeforeFirstUse + 1 !== $firstUsePointer) { $whitespaceBeforeFirstUse .= TokenHelper::getContent($phpcsFile, $pointerBeforeFirstUse + 1, $useStartPointer - 1); } $requiredLinesCountBeforeFirstUse = $this->linesCountBeforeFirstUse; if ( $isAtTheStartOfClass && $this->linesCountBeforeFirstUseWhenFirstInClass !== null ) { $requiredLinesCountBeforeFirstUse = $this->linesCountBeforeFirstUseWhenFirstInClass; } $actualLinesCountBeforeFirstUse = substr_count($whitespaceBeforeFirstUse, $phpcsFile->eolChar) - 1; if ($actualLinesCountBeforeFirstUse === $requiredLinesCountBeforeFirstUse) { return; } $fix = $phpcsFile->addFixableError( sprintf( 'Expected %d line%s before first use statement, found %d.', $requiredLinesCountBeforeFirstUse, $requiredLinesCountBeforeFirstUse === 1 ? '' : 's', $actualLinesCountBeforeFirstUse, ), $firstUsePointer, self::CODE_INCORRECT_LINES_COUNT_BEFORE_FIRST_USE, ); if (!$fix) { return; } $pointerBeforeIndentation = TokenHelper::findPreviousContent( $phpcsFile, T_WHITESPACE, $phpcsFile->eolChar, $firstUsePointer, $pointerBeforeFirstUse, ); $phpcsFile->fixer->beginChangeset(); if ($pointerBeforeIndentation !== null) { FixerHelper::removeBetweenIncluding($phpcsFile, $pointerBeforeFirstUse + 1, $pointerBeforeIndentation); } for ($i = 0; $i <= $requiredLinesCountBeforeFirstUse; $i++) { $phpcsFile->fixer->addNewline($pointerBeforeFirstUse); } $phpcsFile->fixer->endChangeset(); } private function checkLinesAfterLastUse(File $phpcsFile, int $lastUsePointer): void { $tokens = $phpcsFile->getTokens(); /** @var int $lastUseEndPointer */ $lastUseEndPointer = TokenHelper::findNextLocal($phpcsFile, [T_SEMICOLON, T_OPEN_CURLY_BRACKET], $lastUsePointer + 1); if ($tokens[$lastUseEndPointer]['code'] === T_OPEN_CURLY_BRACKET) { $lastUseEndPointer = $tokens[$lastUseEndPointer]['bracket_closer']; } $pointerAfterLastUse = TokenHelper::findNextEffective($phpcsFile, $lastUseEndPointer + 1); $isAtTheEndOfClass = $tokens[$pointerAfterLastUse]['code'] === T_CLOSE_CURLY_BRACKET; $whitespaceEnd = TokenHelper::findNextNonWhitespace($phpcsFile, $lastUseEndPointer + 1) - 1; if ($lastUseEndPointer !== $whitespaceEnd && $tokens[$whitespaceEnd]['content'] !== $phpcsFile->eolChar) { $lastEolPointer = TokenHelper::findPreviousContent( $phpcsFile, T_WHITESPACE, $phpcsFile->eolChar, $whitespaceEnd - 1, $lastUseEndPointer, ); $whitespaceEnd = $lastEolPointer ?? $lastUseEndPointer; } $whitespaceAfterLastUse = TokenHelper::getContent($phpcsFile, $lastUseEndPointer + 1, $whitespaceEnd); $requiredLinesCountAfterLastUse = $isAtTheEndOfClass ? $this->linesCountAfterLastUseWhenLastInClass : $this->linesCountAfterLastUse; if ($requiredLinesCountAfterLastUse === null) { return; } $actualLinesCountAfterLastUse = substr_count($whitespaceAfterLastUse, $phpcsFile->eolChar) - 1; if ($actualLinesCountAfterLastUse === $requiredLinesCountAfterLastUse) { return; } $fix = $phpcsFile->addFixableError( sprintf( 'Expected %d line%s after last use statement, found %d.', $requiredLinesCountAfterLastUse, $requiredLinesCountAfterLastUse === 1 ? '' : 's', $actualLinesCountAfterLastUse, ), $lastUsePointer, self::CODE_INCORRECT_LINES_COUNT_AFTER_LAST_USE, ); if (!$fix) { return; } $phpcsFile->fixer->beginChangeset(); FixerHelper::removeBetweenIncluding($phpcsFile, $lastUseEndPointer + 1, $whitespaceEnd); for ($i = 0; $i <= $requiredLinesCountAfterLastUse; $i++) { $phpcsFile->fixer->addNewline($lastUseEndPointer); } $phpcsFile->fixer->endChangeset(); } /** * @param list $usePointers */ private function checkLinesBetweenUses(File $phpcsFile, array $usePointers): void { if (count($usePointers) === 1) { return; } $tokens = $phpcsFile->getTokens(); $previousUsePointer = null; foreach ($usePointers as $usePointer) { if ($previousUsePointer === null) { $previousUsePointer = $usePointer; continue; } /** @var int $previousUseEndPointer */ $previousUseEndPointer = TokenHelper::findNextLocal($phpcsFile, [T_SEMICOLON, T_OPEN_CURLY_BRACKET], $previousUsePointer + 1); if ($tokens[$previousUseEndPointer]['code'] === T_OPEN_CURLY_BRACKET) { /** @var int $previousUseEndPointer */ $previousUseEndPointer = $tokens[$previousUseEndPointer]['bracket_closer']; } $useStartPointer = $usePointer; $pointerBeforeUse = TokenHelper::findPreviousNonWhitespace($phpcsFile, $usePointer - 1); if (in_array($tokens[$pointerBeforeUse]['code'], Tokens::$commentTokens, true)) { $useStartPointer = TokenHelper::findNext( $phpcsFile, Tokens::$commentTokens, TokenHelper::findPreviousEffective($phpcsFile, $pointerBeforeUse - 1) + 1, ); } $actualLinesCountAfterPreviousUse = $tokens[$useStartPointer]['line'] - $tokens[$previousUseEndPointer]['line'] - 1; if ($actualLinesCountAfterPreviousUse === $this->linesCountBetweenUses) { $previousUsePointer = $usePointer; continue; } $errorParameters = [ sprintf( 'Expected %d line%s between same types of use statement, found %d.', $this->linesCountBetweenUses, $this->linesCountBetweenUses === 1 ? '' : 's', $actualLinesCountAfterPreviousUse, ), $usePointer, self::CODE_INCORRECT_LINES_COUNT_BETWEEN_USES, ]; $pointerBeforeUse = TokenHelper::findPreviousEffective($phpcsFile, $usePointer - 1); if ($previousUseEndPointer !== $pointerBeforeUse) { $phpcsFile->addError(...$errorParameters); $previousUsePointer = $usePointer; continue; } $fix = $phpcsFile->addFixableError(...$errorParameters); if (!$fix) { $previousUsePointer = $usePointer; continue; } $pointerBeforeIndentation = TokenHelper::findPreviousContent( $phpcsFile, T_WHITESPACE, $phpcsFile->eolChar, $usePointer, $previousUseEndPointer, ); $phpcsFile->fixer->beginChangeset(); if ($pointerBeforeIndentation !== null) { FixerHelper::removeBetweenIncluding($phpcsFile, $previousUseEndPointer + 1, $pointerBeforeIndentation); } for ($i = 0; $i <= $this->linesCountBetweenUses; $i++) { $phpcsFile->fixer->addNewline($previousUseEndPointer); } $phpcsFile->fixer->endChangeset(); $previousUsePointer = $usePointer; } } }